#pragma once

#include "Err.hpp"
#include "Epoll.hpp"
#include "Sock.hpp"
#include "Util.hpp"
#include "Protocal.hpp"
#include "LogMessage.hpp"
#include <iostream>
#include <functional>
#include <unordered_map>
#include <cerrno>
#include <cassert>
#include <sys/socket.h>

namespace tcpsvr // 网络版本计算器
{
    class Connection;
    class TcpServer;

    using func_t = std::function<void(Connection *)>;

    const static int defaultport = 8080;
    const static int num = 1024;
    const static int buf_num = 1024;

    class Connection
    {
    public:
        Connection(int sock, TcpServer *tsp) : sock_(sock), tsp_(tsp)
        {
        }

    public:
        void Register(func_t recv, func_t send, func_t excp) // 为文件描述符注册对应的读，写，异常方法
        {
            recver_ = recv;
            sender_ = send;
            excpter_ = excp;
        }

    public:
        int sock_;              // 套接字
        std::string inbuffer_;  // 输入缓冲区
        std::string outbuffer_; // 输出缓冲区

        func_t recver_;  // 读函数
        func_t sender_;  // 写函数
        func_t excpter_; // 异常函数

        TcpServer *tsp_;
    };

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port = defaultport) : func_(func), port_(port), revs_(nullptr)
        {
        }
        ~TcpServer()
        {
            sock_.Close();
            ep_.Close();
            if (revs_)
            {
                delete revs_;
                revs_ = nullptr;
            }
            for (auto it : connections_)
            {
                delete it.second;
            }
        }

    public:
        void initServer() // 初始化服务器
        {
            // logMessage(DEBUG, "in Init");
            if (!ep_.Create())
            {
                logMessage(FATAL, "epoll create failed");
                exit(EPOLL_CRT);
            }
            sock_.Socket();
            sock_.Bind(port_);
            sock_.Listen();
            AddConnection(sock_.FD(), EPOLLIN | EPOLLET,
                          std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr,
                          std::bind(&TcpServer::Excpter, this, std::placeholders::_1));
            // 边缘触发(edge triggle)：当底层的缓冲区获得数据或数据增加的时候，只会向上提醒用户一次
            // 所以需要用户一直向缓冲区获取数据，直到底层没有数据了才能说明本次的数据已经取完了
            // 而且用户也并不知道本次缓冲区的数据有多少，所以需要非阻塞的循环向缓冲区提取数据
            // 因此，我们需要对相应的文件描述符设置为非阻塞状态
            // logMessage(DEBUG, "out Init");
        }
        void Dispatch() // 发送任务
        {
            // logMessage(DEBUG, "in Dispatch");
            int timeout = -1;
            for (;;)
            {
                Loop(timeout);
            }
        }

    private:
        void Loop(int timeout) // 一次循环
        {
            logMessage(DEBUG, "in Loop");
            revs_ = new epoll_event[num];
            int n = ep_.Wait(revs_, num, timeout);
            // logMessage(DEBUG, "n: %d", n);

            if (n == 0)
            {
                logMessage(DEBUG, "time out!");
                return;
            }
            else if (n == -1)
            {
                logMessage(DEBUG, "wait failed");
                return;
            }
            else
            {
                for (int i = 0; i < n; i++)
                {
                    int sock = revs_[i].data.fd;
                    uint32_t events = revs_[i].events;
                    // 为什么 EPOLLIN EPOLLOUT 不能同时设置？
                    // 因为 EPOLLOUT 通常是就绪的，因为只要滑动窗口内还有空间，发送条件就是就绪的
                    // 当输出缓冲区中没有数据的时候，就会一直timeout，此时，对CPU的资源消耗较大

                    logMessage(DEBUG, "sock: %d, connections_[sock]->sock: %d, events: %d", sock, connections_[sock]->sock_, events);

                    if ((events & EPOLLERR) || (events & EPOLLHUP))
                        events |= (EPOLLIN | EPOLLOUT); // 将上面的错误，都看作是读写错误，这样可以做到一个统一处理问题

                    if (IsConnectionExits(sock) && connections_[sock]->recver_ && (events & EPOLLIN))
                    {
                        connections_[sock]->recver_(connections_[sock]);
                    }
                    if (IsConnectionExits(sock) && connections_[sock]->sender_ && (events & EPOLLOUT))
                    {
                        connections_[sock]->sender_(connections_[sock]);
                    }
                }
            }
            logMessage(DEBUG, "out Loop");
        }
        void AddConnection(int sock, uint32_t events, func_t recv, func_t send, func_t excp)
        {
            // logMessage(DEBUG, "in AddConnection");
            // logMessage(DEBUG, "events: %d", events);
            if (events & EPOLLET) // ET模式下，文件描述符需要设置为非阻塞
                Util::SetNoBlock(sock);
            // 创建一个节点，插入到connections_中
            Connection *conn = new Connection(sock, this);
            conn->Register(recv, send, excp);

            bool r = ep_.Addevent(sock, events);
            assert(r);
            (void)r;

            // logMessage(DEBUG, "sock: %d, conn->sock_: %d", sock, conn->sock_);
            connections_.insert(std::make_pair(sock, conn));
            logMessage(DEBUG, "add sock: %d in unordered_map", sock);
        }

    private:
        bool IsConnectionExits(int sock)
        {
            auto pos = connections_.find(sock);
            return pos != connections_.end();
        }
        void Accepter(Connection *conn)
        {
            for (;;)
            {
                uint16_t clientport;
                std::string clientip;
                logMessage(NORMAL, "get a new link......");
                int err = 0;
                int sock = sock_.Accept(&clientip, &clientport, &err);
                if (sock < 0) // 说明此时出错了
                {
                    // 找到对应的错误原因
                    if (err == EAGAIN || err == EWOULDBLOCK)
                        break;
                    else if (err == EINTR)
                        continue;
                    else
                    {
                        logMessage(NORMAL, "connection failed......");
                        if (conn->excpter_)
                            conn->excpter_(conn);
                        return;
                    }
                }
                // uint32_t events = EPOLLIN | EPOLLOUT | EPOLLET;
                // logMessage(DEBUG, "events: %d", events);
                else
                {
                    AddConnection(sock, EPOLLIN | EPOLLET /*| EPOLLOUT*/,
                                  std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Excpter, this, std::placeholders::_1));
                    logMessage(NORMAL, "connection success:[%s: %d]", clientip.c_str(), clientport);
                }
            }
        }
        void Recver(Connection *conn)
        {
            logMessage(DEBUG, "in Recver");
            while (true)
            {
                char buf[buf_num];
                ssize_t n = recv(conn->sock_, buf, sizeof(buf) - 1, 0);
                if (n > 0)
                {
                    buf[n] = 0;
                    conn->inbuffer_ += buf;
                }
                else if (n == 0)
                {
                    logMessage(NORMAL, "client quit!");
                    if (conn->excpter_)
                        conn->excpter_(conn);
                    // 用这个异常函数来关闭文件描述符之类的操作，这样可以提高代码复用率
                    return;
                }
                else
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK)
                        break;
                    else if (errno == EINTR)
                        continue;
                    else
                    {
                        logMessage(ERROR, "recv failed!");
                        if (conn->excpter_)
                            conn->excpter_(conn);
                        return;
                    }
                }
                // 说明此时还没有得到一个完整的报文
                // 此时已经获得了至少一个完整的报文
            }
            func_(conn); // 对数据进行处理
        }
        void Sender(Connection *conn)
        {
            logMessage(DEBUG, "in sender");
            while (true)
            {
                ssize_t n = send(conn->sock_, conn->outbuffer_.c_str(), conn->outbuffer_.size(), 0);
                logMessage(DEBUG, "sender->n: %d", n);
                if (n > 0)
                {
                    if(conn->outbuffer_.empty())
                    {
                        conn->tsp_->EnableReadWrite(conn->sock_, true, false);
                        break;
                    }
                    // 说明此时发送成功，将发送的数据从输出缓冲区中清除
                    else
                        conn->outbuffer_.erase(0, conn->outbuffer_.size());
                }
                // else if (n == 0)
                // {
                //     logMessage(NORMAL, "client quit");
                //     if (conn->excpter_)
                //         conn->excpter_(conn);
                //     return;
                // }
                // 因为该文件描述符被设置为了非阻塞状态，所以不能这样进行操作
                else
                {
                    if (errno == EINTR)
                        continue;
                    else if (errno == EWOULDBLOCK || errno == EAGAIN)
                        break;
                    else
                    {
                        if (conn->excpter_)
                            conn->excpter_(conn);
                        return;
                    }
                }
            }
            if (!conn->outbuffer_.empty())
                conn->tsp_->EnableReadWrite(conn->sock_, true, true);
            else
                conn->tsp_->EnableReadWrite(conn->sock_, true, false);
            logMessage(DEBUG, "out sender");
        }
        void Excpter(Connection *conn) // 将connections_中sock的链接删除，将epoll中的sock删除，将sock关闭
        {
            if (IsConnectionExits(conn->sock_))
            {
                connections_.erase(conn->sock_);
                ep_.Delevent(conn->sock_);
                close(conn->sock_);
            }
        }

    public:
        bool EnableReadWrite(int sock, bool readable, bool writeable)
        {
            uint32_t events = (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET;
            return ep_.Control(sock, events, EPOLL_CTL_MOD);
        }

    private:
        int port_;
        Epoll ep_;
        Sock sock_;
        func_t func_;
        struct epoll_event *revs_;
        std::unordered_map<int, Connection *> connections_;
    };
}